package main

import (
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

var once sync.Once
var dataDir string

func main() {
	once.Do(func() {
		loadYaml()

		runDir, _ := GetCurrentPath()
		dataDir = runDir + "/data/"
		dataDirExists, _ := PathExists(dataDir)
		if !dataDirExists {
			os.Mkdir(dataDir, os.ModePerm)
		}
	})

	fmt.Println("监听地址：", Config.Server.Host+":"+strconv.Itoa(int(Server.Port)))
	fmt.Println("密钥：", Config.AppConfig.Auth)

	// 上传文件
	http.HandleFunc("/upload", uploadHandler)
	// 文件列表
	http.HandleFunc("/list", listHandler)
	// 下载文件
	http.HandleFunc("/download", downloadHandler)
	// 检测文件md5
	http.HandleFunc("/check", checkHandler)
	// index
	http.HandleFunc("/", indexHandler)

	log.Fatal(http.ListenAndServe(Config.Server.Host+":"+strconv.Itoa(int(Config.Server.Port)), nil))
}

func uploadHandler(w http.ResponseWriter, r *http.Request) {
	auth := authTest(w, r)
	if !auth {
		io.WriteString(w, "Auth Fail")
		return
	}

	postMd5 := r.PostFormValue("md5")
	lastMd5, _ := Md5File(getLastFile())
	if strings.EqualFold(postMd5, lastMd5) {
		io.WriteString(w, "备份文件未更改，忽略本次提交")
		return
	}

	file, _, err := r.FormFile("sydata")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer file.Close()

	fileName := dataDir + "SYData-" + time.Now().Format("20060102150405") + ".zip"
	//创建文件
	fW, err := os.Create(fileName)
	if err != nil {
		io.WriteString(w, "创建文件失败")
		return
	}
	defer fW.Close()

	_, err = io.Copy(fW, file)
	if err != nil {
		io.WriteString(w, "保存文件失败")
		return
	}

	io.WriteString(w, "执行成功")
}

func listHandler(w http.ResponseWriter, r *http.Request) {
	auth := authTest(w, r)
	if !auth {
		return
	}

	list := getFileList()
	res, _ := json.Marshal(list)
	io.WriteString(w, string(res))
}

func downloadHandler(w http.ResponseWriter, r *http.Request) {
	auth := authTest(w, r)
	if !auth {
		io.WriteString(w, "Auth Fail")
		return
	}

	fileName := r.FormValue("file")
	fileName = dataDir + "SYData-" + fileName + ".zip"

	exists, _ := PathExists(fileName)
	if !exists {
		fmt.Println("备份文件不存在：" + fileName)
		return
	}

	file, _ := os.Open(fileName)
	defer file.Close()

	fileHeader := make([]byte, 512)
	file.Read(fileHeader)

	fileStat, _ := file.Stat()

	w.Header().Set("Content-Disposition", "attachment; filename="+fileName)
	w.Header().Set("Content-Type", http.DetectContentType(fileHeader))
	w.Header().Set("Content-Length", strconv.FormatInt(fileStat.Size(), 10))

	file.Seek(0, 0)
	io.Copy(w, file)

}

func checkHandler(w http.ResponseWriter, r *http.Request) {
	auth := authTest(w, r)
	if !auth {
		return
	}

	fileName := r.PostFormValue("file")
	fileName = dataDir + "SYData-" + fileName + ".zip"

	exists, _ := PathExists(fileName)
	if !exists {
		return
	}

	fileMd5, _ := Md5File(fileName)

	io.WriteString(w, fileMd5)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
	auth := authTest(w, r)
	if !auth {
		return
	}

	io.WriteString(w, "ok")
}

// ------------------------

// Md5File 计算文件 MD5 值
func Md5File(file string) (value string, err error) {
	data, err := os.ReadFile(file)
	if err != nil {
		return
	}

	md := md5.Sum(data)
	value = fmt.Sprintf("%x", md)
	return
}

// getFileList 获取备份文件列表
func getFileList() []string {
	files, _ := os.ReadDir(dataDir)
	list := make([]string, 0)

	for _, f := range files {
		if find := strings.Contains(f.Name(), "SYData-"); find {
			list = append(list, f.Name()[7:21])
		}
	}

	if len(list) == 0 {
		return list
	}

	sort.Slice(list, func(i, j int) bool {
		numA, _ := strconv.Atoi(list[i])
		numB, _ := strconv.Atoi(list[j])
		return numA > numB
	})

	return list
}

// getLastFile 获取最后一个备份文件
func getLastFile() string {
	list := getFileList()

	if len(list) == 0 {
		return ""
	}

	return dataDir + "SYData-" + list[0] + ".zip"
}

// 授权测试
func authTest(w http.ResponseWriter, r *http.Request) bool {
	postAuth := r.PostFormValue("auth")
	if postAuth == "" {
		postAuth = r.FormValue("auth")
	}

	return postAuth == Config.AppConfig.Auth
}

// PathExists 目录是否存在
func PathExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}
